<?php
/*
 * kernel/object.dbo.php 
 * 
 * Database engine wrapper
 * 
 * Copyright (C) 2010, Taras Kudla
 * 
 */
# statements types
define("_SQL_SELECT",0);
define("_SQL_INSERT",1);
define("_SQL_UPDATE",2);
define("_SQL_DELETE",3);
define("_SQL_CREATETABLE",4);
define("_SQL_DROPTABLE",5);
define("_RETURN_SQL",6);
# sql results types
define("_RESULT_ASSOC",0);
define("_RESULT_LIST",1);
define("_RESULT_BOOL",2);
define("_RESULT_PAIRS",3);
define("_RESULT_ROW",4);
define("_RESULT_ONE",5);
define("_RESULT_NUMROW",6);
define("_RESULT_SINGLE",7);
# fields types definitions
define("_FIELD_CHAR","char");
define("_FIELD_CONSTCHAR","constchar");
define("_FIELD_INT","int");
define("_FIELD_BIGINTAUTO","bigint_auto");
define("_FIELD_INTAUTO","int_auto");
define("_FIELD_BIGINT","bigint");
define("_FIELD_TINYINT","tinyint");
define("_FIELD_SMALLINT","smallint");
define("_FIELD_DATE","date");
define("_FIELD_DATETIME","datetime");
define("_FIELD_TIME","time");
define("_FIELD_TIMESTAMP","timestamp");
define("_FIELD_LONGTEXT","longtext");
define("_FIELD_TEXT","text");
define("_FIELD_FLOAT","float");
define("_FIELD_REAL","real");
define("_FIELD_DOUBLE","double");
define("_FIELD_DECIMAL","decimal");
define("_FIELD_BLOB","blob");
define("_FIELD_BIT","bit");
define("_FIELD_BOOL","bool");
# keys 
define("_KEY_PRIMARY","primary");
define("_KEY_UNIQUE","unique");
define("_KEY_INDEX","index");
define("_KEY_FULLTEXT","fulltext");
final class dbo extends kernel
	{
	public  $driver=null;
	private $drvDir="db/";
	private $drvObj=null;
	private $totalSelect = 0;
	private $totalUpdate = 0;
	private $totalDelete = 0;
	private $totalDirect = 0;
	private $totalInsert = 0;
	private $totalQueries= 0;
	private $totalSelectTime = 0;
	private $totalUpdateTime = 0;
	private $totalInsertTime = 0;
	private $totalDeleteTime = 0;
	private $totalDirectTime = 0;
	private $totalTime		 = 0;
	private $totalConnectTime=0;
	public  $queryProfiler=array();
	private $connected=array();
	# prefix for sql tables
	private $tablesprefix = "gepard_";	
	# supported database engines
	private $supported=array("mysql","mysqlig","pgsql","ibase","sqlite","sqlite3g","mssql");
	# list of system tables, for permissions
	private $systables = array("functions","files","modules","groups","log","levels","levellinks","langterms","users","users_session","users_session_variable","systemoptions","output_areas");
	
    # object initialization
	public function init(){
		
	}
	# connection to all existing drivers in configuration		
	public function connect(){
		/*
		 * Checking and parsing main configuration
		 */
		$dbo = $this->core->get("dboconf");		
		if(isset($dbo) && is_array($dbo) ) {
			foreach($dbo as $k=>$v){
				if(!in_array($k,$this->supported)){
					$this->core->error("DBO","Driver '$k' is unsupported.");
					return false;
				}
				else if( !is_array($v) || count($v)<1 ){
					$this->core->error("DBO","Driver '$k' format is unsupported.");
					return false;
				}
				else {
					$this->load($k,$v);
				}
			}
		} else{  $this->core->error("DBO","DBO config file format is unsupported, cannot connect to database"); return false; }
        return null;
	}
	# probing if can connect to the database engine
	public function probeconnection($driver,$options){
		$tmp = $this->core->get("exceptiononerror");
		$this->core->set("exceptiononerror",true);
		try {
			$link=$this->load($driver,$options,true);
			$link->disconnect();
		} catch ( GepardException $e){
			$this->core->set("exceptiononerror",$tmp);
			return false;
		}
		$this->core->set("exceptiononerror",$tmp);
		return true;
	}
	# loading driver with options as array
	public function load($driver,$options,$returnlink=false){
		# checking if connected already
		if(in_array($driver,$this->connected) && $returnlink==false){
			$this->core->error("DBO","$driver already connected");
			return false;
		} else if( !in_array($driver,$this->supported)){
			$this->core->error("DBO","Database engine '$driver' is not supported");
			return false;			
		}
		$servername=(isset($options["server"])?$options["server"]:"server:$driver");
		$start=microtime(1);
		$this->core->debug("DBO","Trying connect to server ".$servername.", driver :".$driver);
		# checking file
		$file = $this->core->corepath().$this->drvDir."".$driver.".php";
		if( !file_exists($file)){
			$this->core->error("DBO","Driver '$driver' file not found");
			return false;
		}
		require_once($file);
		$total = count($this->connected);
		# creating objects and links
		$link = new $driver($this->core);
		if( !($link instanceof dbostruct)){
			$this->core->error("DBO","Driver $driver is not an instance of dbostruct interface");
			return false;
		}
		if( get_parent_class($link)==_DEFAULT_ABSTRACT_KERNEL)
			$link->init();
		# checking for options
		if( !isset($options["server"]))$options["server"]=null;
		if( !isset($options["db"]))$options["db"]=null;
		if( !isset($options["user"]))$options["user"]=null;
		if( !isset($options["password"]))$options["password"]=null;
		if( !isset($options["port"]))$options["port"]=null;
			
		$link->connect($options["server"],$options["port"],$options["db"],$options["user"],base64_decode($options["password"]));
		if( $returnlink == true){
			return $link;
		}
		if($total==0){
			$this->driver = $driver;
			$this->drvObj = &$link;
		}
		$this->$driver = &$link;
		# setting driver as connected
		$this->core->DEBUG("DBO","Driver connection:  ".$servername.", driver :".$driver." ... Connected");
		$this->connected[]=$driver;
		$this->totalConnectTime+=round(microtime(1)-$start,4);
		$this->core->profiler("DBO",$driver." connection time ",$start);
		return true;
		}
	# getting all connected drivers	
	public function getconnected(){
		return $this->connected;		
	}
	# getting all supported drivers
	public function getsupported(){
		return $this->supported;
	}
	# linking driver as class field
	public function __set($key,$value){
		if(in_array($key,$this->supported)){
			$this->$key=$value;
			$this->core->DEBUG("DBO","Engine: ".$key." linked as source");
			}
		}
	# preparing statement
	public function prepare(){
		
	}	
		
	# default select handler
	public function & select($table,$fields="*",$sql=null,$type=_RESULT_ASSOC){
		$this->totalSelect++;
		$time=microtime(1);
		# checking if multitable query
		if( is_array($table)){
			$tablesql = null;
			$i=0;
			foreach( $table as $k=>$v){
				$tablesql .=($i>0?", ":" ").$k." as $v";
				$i++;
			}
			$table = $tablesql;
		}
		$result=$this->drvObj->select($table,$fields,$sql,$type);
		$this->totalSelectTime+=microtime(1)-$time;
		return $result;
		}
	# inserting
	public function insert($table,array $data,$returnid=null){
		$this->totalInsert++;
		$time = microtime(1);
		if( in_array(trim($table),$this->systables) && $this->core->isloaded("base") && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
			$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables1");
			return false;
		}		
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$result=$this->$v->insert($table,$data,$returnid);
			}			
		} else		
		$result=$this->drvObj->insert($table,$data,$returnid);
		$this->totalInsertTime+=microtime(1)-$time;
		return $result;
		}
		
	public function & update($table,$sql){
		$this->totalUpdate++;
		$time = microtime(1);
		if( in_array(trim($table),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
			$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables2");
			return false;
		}
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$this->$v->update($table,$sql);
			}			
		} else
		$result=$this->drvObj->update($table,$sql);
		$this->totalUpdateTime+=microtime(1)-$time;
		return $result;
		}
	public function & delete($table,$sql){
		$this->totalDelete++;
		$time=microtime(1);
		if( in_array(trim($table),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
			$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables3");
			return false;
		}
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$this->$v->delete($table,$sql);
			}			
		} else
		$result=$this->drvObj->delete($table,$sql);
		$this->totalDeleteTime+=microtime(1)-$time;
		return $result;
		}
		
	public function direct($sql){
		$this->totalDirect++;
		print "DBO statement 'direct' - deprecated";
		return $this->drvObj->execute($sql);
		}
	public function execute($sql){
		$this->totalDirect++;
		return $this->drvObj->execute($sql);		
	}
		
	public function createtable(sqltable $map){
		//if( in_array(trim($map->getname()),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
		//	$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables");
		//	return false;
		//}
		$this->totalDirect++;
		$time=microtime(1);
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$this->$v->createtable($map);
			}
		} else
		$this->drvObj->createtable($map);
		$this->totalDirectTime+=microtime(1)-$time;
		return null;	
	}
	
	public function droptable($table){
		//if( in_array(trim($table),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
		//	$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables");
		//	return false;
		//}
		$this->totalDirect++;
		$time=microtime(1);
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$this->$v->droptable($table);
			}			
		} else		
		$this->drvObj->droptable($table);
		$this->totalDirectTime+=microtime(1)-$time;
	}
	
	public function gettables($database=null){
		$this->totalDirect++;
		return $this->drvObj->gettables($this->core->escapeString($database));
	}
	
	public function getkeys($table){
		$this->totalDirect++;
		return $this->drvObj->getkeys($table);
	}	
	
	public function getdatabases(){
		$this->totalDirect++;
		return $this->drvObj->getdatabases();
	}
	public function getfields($table){
		$this->totalDirect++;
		return $this->drvObj->getfields($table);
	}

	public function getdatabase(){
		$this->totalDirect++;
		return $this->drvObj->getdatabase();
	}
	
	/*
	 * TODO: 
	 */
	public function getstats(){
		$stats=array();
		$total = $this->totalSelect+$this->totalUpdate+$this->totalDelete+$this->totalInsert+$this->totalDirect;
		$totaltime=$this->totalDirectTime+$this->totalSelectTime+$this->totalInsertTime+$this->totalUpdateTime+$this->totalDeleteTime;
		$totaltime=round($totaltime,4);
		$stats["totalnum"]=$total;
		$stats["totaltime"]=$totaltime;
		$stats["datareplication"]=($this->core->get("dbodatareplication"))?"true":"false";
		$stats["totalconnected"]=count($this->connected);
		$stats["connected"]=$this->connected;
		$stats["connectime"]=$this->totalConnectTime;
		$stats["Select"]=array("num"=>$this->totalSelect,"time"=>round($this->totalSelectTime,4));
		$stats["Update"]=array("num"=>$this->totalUpdate,"time"=>round($this->totalUpdateTime,4));
		$stats["Insert"]=array("num"=>$this->totalInsert,"time"=>round($this->totalInsertTime,4));
		$stats["Delete"]=array("num"=>$this->totalDelete,"time"=>round($this->totalDeleteTime,4));
		$stats["Direct"]=array("num"=>$this->totalDirect,"time"=>round($this->totalDirectTime,4));
		return $stats;
	}
	public function tableexists($table){
		$this->totalDirect++;
		return $this->drvObj->tableexists($table);	
	}
	
	public function version(){
		return $this->drvObj->version();
	}
	
	public function engine(){
		return $this->connected[0];
	}
	# disconnecting all connections	
	public function disconnect($dbdrv = null)
		{
		if($dbdrv!=null && in_array($dbdrv,$this->connected) ){
			is_object($this->$dbdrv)?$this->$dbdrv->disconnect():null;
			
			unset($this->connected[array_search($dbdrv,$this->connected)]);
			return true;
		}		
		foreach($this->connected as $k=>$v){
		
			is_object($this->$v)?$this->$v->disconnect():null;
			unset($this->connected[$k]);
		}
		
		}
	
	function __destruct(){
	}

	}
/*
 * Default sqltable class, use it for tables creating
 */
	final class sqltable{
		private $name = "";
		private $fields = array();
		private $keys	= array();
		private $encoding = null;
		private $engine		= null;
		public  $droponexist = true;
	function __construct($name){
		$this->name = $name;
	}
	public function addfield($name,$type,$size=null,$default=null,$null=false){
		$this->fields[]=array($name,$type,$size,$default,$null);
	}
	
	public function addkey($type,$fieldname){
		$this->keys[]=array($type,$fieldname);		
	}
	
	public function getname(){
		return $this->name;
	}
	
	public function get(){
		return array($this->name,$this->fields,$this->keys,$this->encoding,$this->engine);			
	}
	public function encoding($encoding){
		$this->encoding = $encoding;
	}
	public function engine($engine){
		$this->engine	= $engine;
	}
	
	function __destruct(){
		
	}
}	
?>